local rtk=require('libluartk')
local debug_xml
local M={}

local gx=require 'libgamex'
local dbg=require 'wxExt.debug'
local B=require 'wxExt.ui_builder'
local tparser=require 'libgamex.tpl_parser'

--tparser=require 'gamex.tpl_parser'
--table_each(require 'gamex.utils',function(k,v)
--	gx[k]=v
--end)
local template=rtk.getprop('app.binDir') .. '/lua/games/heroes3/data/hero.tpl'
local obj_types=tparser.load_winhex_template(template)
local hero_type=obj_types.HERO

local secondary_skills={
"寻路术", "箭术", "后勤学", "侦察术", "外交术",
"航海术", "领导术", "智慧术","神秘术", "幸运术",
"弹道术", "鹰眼术", "招魂术", "理财术", "火系魔法",
"气系魔法","水系魔法", "土系魔法", "学术", "战术",
"炮术", "学习能力", "进攻术", "防御术", "智力",
"魔力","抵抗力","急救术"
}

function readRemoteObj(addr,tdef)
	local buf=gx.CBuffer.create()
	buf:load(gx:peek(addr,tdef.FSize))
	local obj=gx.CAccessor.create(tdef)
	obj:bind(buf)
	return obj
end

function probeHeroList(hero_dlg)
	local startAddr=hero_dlg.FAddr
	local tp=hero_dlg.FObj.FDef
	local heroes={}
	function getHero(addr)
		local buf=gx.CBuffer.create()
		buf:load(gx:peek(addr,tp.FSize))
		local hero_obj=gx.CAccessor.create(tp)
		hero_obj:bind(buf)
		return rtk.iconv('gb2312','utf-8',hero_obj.basic.名字)
	end
	local addr
	for i=0,-200,-1 do
		addr=startAddr+i*tp.FSize
		local ok,n=pcall(getHero,addr)
		if ok and n then
			rtk.dbg_print('Probed %s of %d at %x\n',n,i,addr)
			table.insert(heroes,1,{name=n,addr=addr})
		else
			rtk.dbg_print('Probing %d at %x failed,%s.\n',i,addr,n)
			break
		end
	end
	for i=1,200 do
		addr=startAddr+i*tp.FSize
		local ok,n=pcall(getHero,addr)
		if ok and n then
			rtk.dbg_print('Probed %s of %d at %x\n',n,i,addr)
			table.insert(heroes,{name=n,addr=addr})
		else
			rtk.dbg_print('Probing %d at %x failed,%s.\n',i,addr,n)
			break
		end
	end
	return heroes
end

function setCustomizedSkills(hero_obj)
	local DlgTemp=B.TDialog{
		title='自定义技能',sizeable=true,
		B.TList{
			id='listSkills',title='技能列表',
			flags='LB_MULTIPLE',
			choices=secondary_skills,
			expand=true,proportion=1,minsize={300,500},
		},
		B.THBox{
			B.TButton{id='Ok',wid=wx.wxID_OK},
			B.TButton{id='Cancel',wid=wx.wxID_CANCEL}
		},
		onCreate=function(self,hero)
			local i
			for i=1,#secondary_skills do
				local sk=secondary_skills[i]
				rtk.dbg_print('%s = %d\n', sk, hero.Skills.levels[sk])
				if hero.Skills.levels[sk] ~= 0 then
					self.listSkills:SetSelection(i-1,true)
				end
			end
			self:Fit()
			self:SetMinSize(self:GetSize())
		end
	}
	local dlg=DlgTemp:create(nil,hero_obj)
	if dlg:ShowModal() == wx.wxID_OK then
		local idx=1
		for i=1,#secondary_skills do
			local sk=secondary_skills[i]
			if dlg.listSkills:IsSelected(i-1,wx) then
				rtk.dbg_print('Setting %s\n', sk)
				hero_obj.Skills.levels[sk]=3
				hero_obj.Skills.sequence[sk]=idx
				idx=idx+1
				if idx>8 then break	end
			end
		end
		return true
	end
end

local hero_templates={
	-- 魔法型
	{name='魔法+外交',skills={'智慧术','智力','火系魔法','土系魔法','气系魔法','水系魔法','领导术','外交术'}},
	{name='魔法+学术+箭术',skills={'智慧术','智力','火系魔法','土系魔法','气系魔法','水系魔法','箭术','学术'}},
	-- 鬼族
	{name='箭术+招魂',skills={'智慧术','智力','火系魔法','土系魔法','气系魔法','水系魔法','箭术','招魂术'}},
	{name='自定义'},
}

local TEditHeroDialog=B.TDialog{
	title='Object editor demo',
	sizeable=true,
	B.THBox{
		expand=true,
		B.TLabel{'Heroes:'},
		B.TCombo{id='cmbHeroes',expand=true,proportion=1,readonly=true}
	},
	B.TNotebook{
		expand=true,proportion=1,
		minsize={640,480},
		B.TPanel{
			title='基本信息',
			gx.__type_to_prop_sheet(obj_types.HERO_BASIC,B){
				id='basic',expand=true,proportion=1
			},
		},
		B.TPanel{
			title='技能',
			B.THBox{
				expand=true,
				B.TLabel{'技能模板',border=1},
				B.TCombo{id='cmbHeroTemp',readonly=true,proportion=1},
				B.TButton{id='btnSetHeroTemp',title='修改技能'},
			},
			gx.__type_to_prop_sheet(hero_type.subtypes.Struct_Skills,B){
				id='skills',expand=true,proportion=1
			},
		},
		B.TPanel{
			title='生物',
			gx.__type_to_prop_sheet(hero_type.subtypes.Struct_units,B){
				id='units',expand=true,proportion=1
			},
		},
		B.TPanel{
			title='随身宝物',
			gx.__type_to_prop_sheet(obj_types.HERO_BODY_ARTIFACTS,B){
				id='随身宝物',expand=true,proportion=1
			},
		},
		B.TPanel{
			title='背囊',
			gx.__type_to_prop_sheet(obj_types.HERO_BAG_ARTIFACTS,B){
				id='背囊',expand=true,proportion=1
			},
		},
		B.TPanel{
			title='魔法',
			gx.__type_to_prop_sheet(obj_types.HERO_MAGIC,B){
				id='魔法1',expand=true,proportion=1
			},
		},
		debug_xml and B.TPanel{
			title='Structure',
			B.TWebView{id='xmlStructure',
				expand=true,proportion=1,
				backend=wxwebview.wxWebViewBackendWebKit
			}
		}
	},
	B.THBox{
		expand=true,
		B.TStretch{proportion=1},
		B.TButton{id='Ok'},
		B.TButton{id='Reload'},
		B.TButton{id='Apply'},
		B.TButton{id='Previous'},
		B.TButton{id='Next'},
	},
	--rtk.getpropFlag('debug') and 
	dbg.ID_DEBUG_BAR{id='dbg'},
	Ok_onClick=function(self)
		self:Apply_onClick()
		self:Hide()
	end,
	doRefresh=function(self)
		local hero=self.FObj
		self.Title=string.format('Hero:%s @ (0x%x)',hero.basic.名字,self.FAddr)
		self.basic:doRefresh()
		self.skills:doRefresh()
		self.units:doRefresh()
		self.随身宝物:doRefresh()
		self.背囊:doRefresh()
		self.魔法1:doRefresh()
	end,
	Reload_onClick=function(self)
		self:doReload()
	end,
	Apply_onClick=function(self)
		self:doApply()
	end,
	Previous_onClick=function(self)
		self.FAddr=self.FAddr-hero_type.FSize
		self:doReload()
	end,
	Next_onClick=function(self)
		self.FAddr=self.FAddr+hero_type.FSize
		self:doReload()
	end,
	cmbHeroes_onCombobox=function(self)
		local idx=self.cmbHeroes:GetSelection()+1
		self.FAddr=self.FHeroList[idx].addr
		self:doReload()
	end,
	doCreate=function(self,addr)
		self:Fit()
		rtk.dbg_print('Show hero at 0x%x\n',addr)
		self:SetMinSize(self:GetSize())
		local buf=gx.CBuffer.create()
		buf:load(gx:peek(addr,hero_type.FSize))
		local hero_obj=gx.CAccessor.create(hero_type)
		hero_obj:bind(buf)
		self.FObj=hero_obj
		self.FAddr=addr
		self.FBuffer=buf
		self.F_Env.FObj=hero_obj
		local basic=hero_obj.basic
		self.basic.FObj=basic
		basic:setEncoding('gb2312')
		self.skills.FObj=hero_obj.Skills
		self.units.FObj=hero_obj.units
		self.随身宝物.FObj=hero_obj.随身宝物
		self.背囊.FObj=hero_obj.背囊
		self.魔法1.FObj=hero_obj.magic_1
		rtk.table_ieach(hero_templates,function(_,tmp)
			self.cmbHeroTemp:Append(tmp.name)
		end)
		self.cmbHeroTemp:SetSelection(0)
		if debug_xml then
			local xml={}
			table.insert(xml,'<?xml version="1.0" encoding="UTF-8"?>')
			table.insert(xml,'<object>')
			table.insert(xml,rtk.tableToXml(hero_type))
			table.insert(xml,'</object>')
			local tmpfile=os.tmpname() .. '.xml'
      rtk.writeFile(tmpfile,table.concat(xml))
			self.xmlStructure:LoadURL(tmpfile)
		end
		self:doRefresh()
		local hlist=probeHeroList(self)
		rtk.table_ieach(hlist,function(_,h)
			self.cmbHeroes:Append(h.name)
		end)
		self.FHeroList=hlist
		self.cmbHeroes.Value=hero_obj.basic.名字
	end,
	btnSetHeroTemp_onClick=function(self)
		local idx=self.cmbHeroTemp:GetSelection()
		local hero_obj=self.F_Env.FObj
		if idx==self.cmbHeroTemp.Count-1 then
			if setCustomizedSkills(hero_obj) then
				self.skills:doRefresh()
			end
		else
			idx=idx+1
			local tmp=hero_templates[idx]
			hero_obj.Skills:memset(0)
			rtk.table_ieach(tmp.skills,function(_,sk)
				hero_obj.Skills.levels[sk]=3
				hero_obj.Skills.sequence[sk]=_
			end)
			hero_obj.Skills.count=#tmp.skills
			self.skills:doRefresh()
		end
	end,
	doReload=function(self)
		local addr=self.FAddr
		rtk.printk(2,'Reading %d bytes from %x\n',hero_type.FSize,addr)
		self.FBuffer:load(gx:peek(addr,hero_type.FSize))
		self:doRefresh()
	end,
	doApply=function(self)
		local addr=self.FAddr
		local hero_obj=self.F_Env.FObj
		hero_obj.magic_2:copy(hero_obj.magic_1)
		local data=self.FBuffer:toString()
		rtk.printk(2,'Save %d bytes to %x\n',#data,addr)
		gx:poke(addr,data)
	end
}
--[[
function show_hero(buffer,parent)
	local hero_obj
	if type(buffer)=='string' then
		local bufobj=gx.CBuffer.create()
		bufobj:load(buffer)
		hero_obj:bind(bufobj)
	else
		hero_obj=buffer
	end
	local dlg=TEditHeroDialog:create(parent,hero_obj,0)
	dlg:Show()
end
]]

function show_hero_remote(addr,parent)
	local dlg=TEditHeroDialog:create(parent,addr)
	dlg:Show()
end

-- 按照名字和过滤条件搜索hero数据块，pred是合法的lua表达式，可直接访问hero_type的
-- 字段，例如：basic.经验值==1056 and units.数量[1]==9
local function searchHeroByName(name,pred)
	local pid=gx.FProc.id
	name=rtk.iconv('utf-8','gbk',name)
	rtk.dbg_print('Search hero by name "%s"\n',gx.string_to_hex(name))
	local addrs=gx.searchProcessString(pid,name)
	local ret={}
	local name_offset=hero_type.items.basic.FOffset+hero_type.items.basic.type.items.名字.FOffset
	pred='return ' .. pred
	for _,a in ipairs(addrs) do
		a=addrs[_]
		local buf_obj=gx.CBuffer.create()
		a=a-name_offset
		rtk.dbg_print('%d - Trying 0x%x\n',_,a)
		local buffer=gx:peek(a,hero_type.FSize)
		if buffer then
			buf_obj:load(buffer)
			local hero_obj=gx.CAccessor.create(hero_type)
			hero_obj:bind(buf_obj)
			local ok,err
			local env={}
			rtk.table_ieach(hero_type.items,function(_,it)
				if it.name then
					--dbg_print('Binding %s\n',it.name)
					env[it.name]=hero_obj[it.name]
				end
			end)
			ok,err=load(pred,'checker','t',env)
			assert(ok,err)
			ok,err=pcall(ok)
			assert(ok,err)
			if err==true then
				table.insert(ret,a)
			end
			collectgarbage('collect')
		end
	end
	return ret
end

function searchHero(method,...)
	local str
	local parts={...}
	local addrs={}
	local offset
	local pid=gx.FProc.id
	if method=='exp' then
		str=gx.int_to_hex(tonumber(parts[1]),4)
		str=str .. gx.int_to_hex(tonumber(parts[2]),2)
		offset=-81
	elseif method=='addr' then
		return {...}
	elseif method==nil then
		local all={}
		rtk.table_ieach(parts,function(_,p)
			local num=tonumber(p)
			table.insert(all,gx.int_to_hex(num,4))
		end)
		str=table.concat(all)
		offset=-145-28
	elseif type(method)=='string' then
		return searchHeroByName(method,...)
	end
	rtk.dbg_print('Searching "%s"\n', gx.string_to_hex(str))
	addrs=gx.searchProcessString(pid,str)
	rtk.table_ieach(addrs,function(a,_)
		addrs[a]=addrs[a]+offset
	end)
	return addrs
end

function editHero(method,...)
	local addrs=searchHero(method,...)
	-- check if called from within gamex gui
	if #addrs>1 then
		if gx.updateAddressList then
			gx.updateAddressList(addrs)
		else
			rtk.tprint(addrs)
		end
	elseif #addrs==1 then
		show_hero_remote(addrs[1])
	else
		rtk.printk(2,'Error : hero not found.\n')
	end
end

function doEditResource(addr)
	local DlgDef=gx.object_editor(obj_types.H3_RESOURCES,
		{
			title='资源编辑器',
			minsize={300,350},
			encoding='gb2312',
			buttons={previous=true,next=true}
		}
	)
	DlgDef:create(nil,addr):Show()
end

function editResource(...)
	local vals={...}
	local k=''
	rtk.table_ieach(vals,function(_,v)
		k=k .. gx.write_uint32(v)
	end)
	assert(#k>4,'Must set at least two integers')
	local pid=gx.FProc.id
	rtk.dbg_print('Searching "%s"\n', gx.string_to_hex(k))
	local addrs=gx.searchProcessString(pid,k)
	if #addrs==1 then
		doEditResource(addrs[1])
		return
	elseif #addrs>1 then
		if gx.updateAddressList then
			gx.updateAddressList(addrs)
		else
			rtk.tprint(addrs)
		end
		for _,a in ipairs(addrs) do
			if gx:peek(a+0x30,4)=='\xcd\xe6\xbc\xd2' then
				doEditResource(a)
				return
			end
		end
	end
	wx.wxMessageBox('Not found.')
end

function M.t0()
	local fh
	local test_file
--	test_file='c:/tmp/h2.bin'
	test_file='heroes3/test/hero.bin'
	fh=io.open(test_file,'rb')
	local buffer=fh:read('*a')
	fh:close()
	local buf=gx.CBuffer.create()
	buf:load(buffer)
	local hero_obj=gx.CAccessor.create(hero_type)
	hero_obj:bind(buf)
	local dlg=TEditHeroDialog:create(nil,hero_obj)
	dlg.FReloadProc=function()
		fh=io.open(test_file,'rb')
		local buffer=fh:read('*a')
		fh:close()
		local buf_obj=gx.CBuffer.create()
		buf_obj:load(buffer)
	end
	dlg.FApplyPrpc=rtk.getpropFlag('write_test') and function()
		fh=io.open(test_file,'wb')
		fh:write(buf:toString())
		fh:close()
	end
	dlg:ShowModal()
end

function M.t1(method,...)
	local h3proc=gx.enumProcesses('Heroes3FC HD.exe')[1]
	rtk.dbg_print('Heroes3FC HD.exe=%d\n', h3proc.id)
	gx.FProc=h3proc
	gx.FhProcess=gx.openProcess(h3proc.id)
	editHero(method,...)
end

function M.t2(a)
	local h3proc=gx.enumProcesses('Heroes3FC HD.exe')[1]
	rtk.dbg_print('Heroes3FC HD.exe=%d\n', h3proc.id)
	gx.FProc=h3proc
	gx.FhProcess=gx.openProcess(h3proc.id)
	doEditResource(a)
	wx.wxGetApp():MainLoop()
end

gx.addInterpreter(
	{title='Heroes MM3 Hero',func=show_hero_remote}
)

M.hero_type=hero_type

return M
